package scanner;

import java.io.BufferedReader;

import scanner.Token.TokenType;

public class CMinusScanner implements Scanner {
	public enum ScannerState{
		START,				// 0, 10
		INDIGIT,			// 1
		INID,				// 2
		INDIV_OR_COMMENT,	// 6
		INCOMMENT,			// 7
		CLOSING_COMMENT,	// 9
		LT_OR_LTE,			// 11
		GT_OR_GTE,			// 13
		ASSIGN_OR_COMPARE,	// 15
		INNOT_EQUAL,		// 17
		DONE				// 3,4,5,12,14,,16,1819,20,21,22,23,24,25,26
	}

	private BufferedReader inFile;
	private Token nextToken;
        private Token currToken;
	private char nextChar;
	
	public CMinusScanner(BufferedReader file){
		try{
			inFile = file;
			nextChar = (char)inFile.read();
			nextToken = scanToken();
		}
		catch(Exception e){
			System.err.println("ERROR: " + e);
		}
	}

	public Token getNextToken(){
		Token newToken = new Token(nextToken.getType(), nextToken.getData());
		if(nextToken.getType() != Token.TokenType.EOF_TOKEN){
			nextToken = null;
			nextToken = scanToken();
		}
                currToken = newToken;
		return newToken;
	}
	
	public Token viewNextToken(){
		return nextToken;
	}
	
        public Token getCurrentToken(){
            return currToken;
        }
	/**************************** SCAN CLASS *************************/
	private Token scanToken() {
		TokenType returnType = TokenType.ERROR_TOKEN;
		String tokenString = "";
		boolean save, munch;
		boolean checkIfKeyword = false;
		
		ScannerState state = ScannerState.START;
		
		while(state != ScannerState.DONE){
			save = false;
			munch = true;
			char c = viewNextChar();
			
			switch(state){
			case START:
				if(isDigit(c)){
					state = ScannerState.INDIGIT;
					save = true;
				}
				else if(isAlpha(c)){
					state = ScannerState.INID;
					save = true;
				}
				else if(isWhitespace(c)){
					// skip white-space
				}
				else if(c == '/'){
					state = ScannerState.INDIV_OR_COMMENT;
				}
				else if(c == '<'){
					state = ScannerState.LT_OR_LTE;
				}
				else if(c == '>'){
					state = ScannerState.GT_OR_GTE;
				}
				else if(c == '='){
					state = ScannerState.ASSIGN_OR_COMPARE;
				}
				else if(c == '!'){
					state = ScannerState.INNOT_EQUAL;
				}
				else{
					state = ScannerState.DONE;
					switch(c){
					case (char)-1:
						returnType = TokenType.EOF_TOKEN;
						break;
					case '+':
						returnType = TokenType.PLUS_TOKEN;
						break;
					case '-':
						returnType = TokenType.MINUS_TOKEN;
						break;
					case '*':
						returnType = TokenType.MULT_TOKEN;
						break;
					case ';':
						returnType = TokenType.SEMICOLON_TOKEN;
						break;
					case ',':
						returnType = TokenType.COMMA_TOKEN;
						break;
					case '(':
						returnType = TokenType.OPAREN_TOKEN;
						break;
					case ')':
						returnType = TokenType.CPAREN_TOKEN;
						break;
					case '[':
						returnType = TokenType.OBRACE_TOKEN;
						break;
					case ']':
						returnType = TokenType.CBRACE_TOKEN;
						break;
					case '{':
						returnType = TokenType.OCURLY_TOKEN;
						break;
					case '}':
						returnType = TokenType.CCURLY_TOKEN;
						break;
					default:
						returnType = TokenType.ERROR_TOKEN;
					}
				}
				break;
			case INDIGIT:
				save = true;
				if(isDigit(c)){
					// continue getting the digits
				}
				else if(isAlpha(c)){
					// if the char is not a digit or whitespace then it is an error
					state = ScannerState.DONE;
					returnType = TokenType.ERROR_TOKEN;
				}
				else{
					// digit is done
					state = ScannerState.DONE;
					returnType = TokenType.NUM_TOKEN;
					save = false;
					munch = false;
				}
				break;
			case INID:
				save = true;
				if(isAlpha(c)){
					// continue getting the letters
				}
				else if(isDigit(c)){
					state = ScannerState.DONE;
					returnType = TokenType.ERROR_TOKEN;
				}
				else{
					// the id/keyword is done
					state = ScannerState.DONE;
					checkIfKeyword = true;
					save = false;
					munch = false;
				}
				break;
			case INDIV_OR_COMMENT:
				if(c == '*'){
					// its a comment
					state = ScannerState.INCOMMENT;
				}
				else{
					// its just a divide
					state = ScannerState.DONE;
					returnType = TokenType.DIV_TOKEN;
					munch = false;
				}
				break;
			case INCOMMENT:
				if(c == '*'){
					state = ScannerState.CLOSING_COMMENT;
				}
				break;
			case CLOSING_COMMENT:
				if(c == '/'){
					// the comment is finished
					// since we just ignore comments start over looking for a token
					state = ScannerState.START;
				}
                                else if(c != '*'){
					state = ScannerState.INCOMMENT;
				}
				break;
			case LT_OR_LTE:
				state = ScannerState.DONE;
				if(c == '='){
					returnType = TokenType.LTE_TOKEN;
				}
				else{
					munch = false;
					returnType = TokenType.LT_TOKEN;
				}
				break;
			case GT_OR_GTE:
				state = ScannerState.DONE;
				if(c == '='){
					returnType = TokenType.GTE_TOKEN;
				}
				else{
					munch = false;
					returnType = TokenType.GT_TOKEN;
				}
				break;
			case ASSIGN_OR_COMPARE:
				state = ScannerState.DONE;
				if(c == '='){
					returnType = TokenType.EQUALITY_TOKEN;
				}
				else{
					munch = false;
					returnType = TokenType.ASSIGN_TOKEN;
				}
				break;
			case INNOT_EQUAL:
				state = ScannerState.DONE;
				if(c == '='){
					returnType = TokenType.NOT_EQUAL_TOKEN;
				}
				else{
					munch = false;
					returnType = TokenType.ERROR_TOKEN;
				}
				break;
			case DONE:
			default:
				munch = false;
				returnType = TokenType.ERROR_TOKEN;
			}
			
			// check if we should save the character
			if(save){
				tokenString += c;
			}
			
			// check if we should consume the character
			if(munch){
				getNextChar();
			}
		}
		
		// check if we have an ID/keyword
		if(checkIfKeyword){
			if(tokenString.equals("if")){
				returnType = TokenType.IF_TOKEN;
			}
			else if(tokenString.equals("else")){
				returnType = TokenType.ELSE_TOKEN;
			}
			else if(tokenString.equals("int")){
				returnType = TokenType.INT_TOKEN;
			}
			else if(tokenString.equals("return")){
				returnType = TokenType.RETURN_TOKEN;
			}
			else if(tokenString.equals("void")){
				returnType = TokenType.VOID_TOKEN;
			}
			else if(tokenString.equals("while")){
				returnType = TokenType.WHILE_TOKEN;
			}
			else{
				returnType = TokenType.ID_TOKEN;
			}
		}

		// get the data for the token
		Object data = null;
		if(returnType == TokenType.NUM_TOKEN ||
				returnType == TokenType.ID_TOKEN){
                        if(returnType == TokenType.NUM_TOKEN){
                            data = Integer.parseInt(tokenString);
                        } else {
                            data = tokenString;
                        }
		}
		
		Token returnToken = new Token(returnType, data);
		
		return returnToken;
	}
	
	
	/********************** helping functions **********************/
	private char viewNextChar(){
		return nextChar;
	}

	public char getNextChar(){
		char returnChar = nextChar;
		try{
			if(nextChar != -1){
				nextChar = (char)inFile.read();
			}
		}
		catch(Exception e){
			System.err.println("ERROR: " + e);
		}
		return returnChar;
	}
	
	public boolean isDigit(char c){
		if(c >= '0' && c <= '9'){
			return true;
		}
		return false;
	}
	
	public boolean isAlpha(char c){
		if((c >= 'A' && c <= 'Z') ||
		   (c >= 'a' && c <= 'z')){
			return true;
		}
		return false;
	}
	
	private boolean isWhitespace(char c){
		if(c == ' ' || c == '\t' || c == '\n' || c == '\r'){
			return true;
		}
		return false;
	}
}
